package com.lhj.xxsc.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.xxsc.common.to.es.SkuEsModel;
import com.xxsc.common.utils.R;
import com.lhj.xxsc.search.config.XxscElasticSearchConfig;
import com.lhj.xxsc.search.constant.EsConstant;
import com.lhj.xxsc.search.feign.ProductFeignService;
import com.lhj.xxsc.search.service.MallSearchService;
import com.lhj.xxsc.search.vo.AttrTo;
import com.lhj.xxsc.search.vo.BrandTo;
import com.lhj.xxsc.search.vo.SearchParam;
import com.lhj.xxsc.search.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author 罗惠江
 * @version 1.0
 * @email 1511619294@qq.com
 * @date 2020/12/12 23:15
 */
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {
	// 定义我们使用到的下划线
	static final String UNDERSCORE = "_";
	static final String ASC = "ASC";
	static final String COLON = ":";

	@Autowired
	private RestHighLevelClient client;

	@Autowired
	private ProductFeignService productFeignService;

	public MallSearchServiceImpl() {
	}

	// 去 es 检索出
	@Override
	public SearchResult search(SearchParam param) {
		// 1.动态的去构建出查询需要的DSL语句
		SearchResult result = null;
		SearchRequest searchRequest = buildSearchRequrest(param);

		try {
			// 2.执行检索请求
			SearchResponse response = client.search(searchRequest, XxscElasticSearchConfig.COMMON_OPTIONS);

			// 3.分析响应数据封装成我们需要的格式
			result = buildSearchResult(response, param);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return result;
	}

	/**
	 * 构建结果数据
	 *
	 * @param response
	 * @param param
	 * @return
	 */
	private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
		SearchResult result = new SearchResult();
		SearchHits hits = response.getHits();

		// 返回的所有查询到的商品
		List<SkuEsModel> esModels = new ArrayList<>();
		if (null != hits.getHits() && hits.getHits().length > 0) {
			for (SearchHit hit : hits.getHits()) {
				String sourceAsString = hit.getSourceAsString();
				SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
				// 高亮
				if (!StringUtils.isEmpty(param.getKeyword())) {
					HighlightField skuTitleHigh = hit.getHighlightFields().get(EsConstant.SKU_TITLE);
					String skuTitle = skuTitleHigh.getFragments()[0].string();
					esModel.setSkuTitle(skuTitle);
				}
				esModels.add(esModel);
			}
		}
		result.setProducts(esModels);

		// 分类信息
		List<SearchResult.CatalogVo> catalogVoList = new ArrayList<>();
		ParsedLongTerms catalogAgg = response.getAggregations().get(EsConstant.CATALOG_AGG);
		List<? extends Terms.Bucket> catalogBuckets = catalogAgg.getBuckets();
		for (Terms.Bucket bucket : catalogBuckets) {
			SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
			String keyAsString = bucket.getKeyAsString();
			long catalogId = Long.parseLong(keyAsString);
			catalogVo.setCatalogId(catalogId);
			// 分类子聚合
			ParsedStringTerms catalogNameAgg = bucket.getAggregations().get(EsConstant.CATALOG_NAME_AGG);
			String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
			catalogVo.setCatalogName(catalogName);
			catalogVoList.add(catalogVo);
		}
		result.setCatalogs(catalogVoList);


		// 当前所有商品设计到的所有属性信息
		List<SearchResult.AttrVo> attrVoList = new ArrayList<>();
		ParsedNested attrsAgg = response.getAggregations().get(EsConstant.ATTR_AGG);
		ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get(EsConstant.ATTRS_ATTR_ID_AGG);
		for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
			SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
			// 属性id
			long attrId = bucket.getKeyAsNumber().longValue();

			// 属性名
			ParsedStringTerms attrNameAgg = bucket.getAggregations().get(EsConstant.ATTRS_ATTR_NAME_AGG);
			String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
			// 属性值
			ParsedStringTerms attrValueAgg = bucket.getAggregations().get(EsConstant.ATTRS_ATTR_VALUE_AGG);
			List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> {
				String keyAsString = item.getKeyAsString();
				return keyAsString;
			}).collect(Collectors.toList());

			attrVo.setAttrId(attrId);
			attrVo.setAttrName(attrName);
			attrVo.setAttrValue(attrValues);
			attrVoList.add(attrVo);
		}

		result.setAttrs(attrVoList);


		// 当前所有商品涉及的品牌信息
		List<SearchResult.BrandVo> brandVoList = new ArrayList<>();
		ParsedLongTerms brandAgg = response.getAggregations().get(EsConstant.BRAND_AGG);
		for (Terms.Bucket bucket : brandAgg.getBuckets()) {
			SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
			// 品牌 id
			long brandId = bucket.getKeyAsNumber().longValue();
			// 品牌 名
			ParsedStringTerms brandNameAgg = bucket.getAggregations().get(EsConstant.BRAND_NAME_AGG);
			String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
			// 品牌 图
			ParsedStringTerms brandImgAgg = bucket.getAggregations().get(EsConstant.BRAND_IMG_AGG);
			String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
			brandVo.setBrandId(brandId);
			brandVo.setBrandName(brandName);
			brandVo.setBrandImg(brandImg);
			brandVoList.add(brandVo);
		}
		result.setBrands(brandVoList);

		// 总记录数
		long total = hits.getTotalHits().value;
		result.setTotal(total);

		int totalPages = total % EsConstant.PRODUCT_PAG_SIZE == 0 ? (int) total / EsConstant.PRODUCT_PAG_SIZE : ((int) total / EsConstant.PRODUCT_PAG_SIZE + 1);
		// 总页码
		result.setTotalPages(totalPages);
		// 分页页码
		result.setPageNum(param.getPageNum());

		// 制作页码数组
		List<Integer> pageNavs = new ArrayList<>();
		for (int i = 1; i <= totalPages; i++) {
			pageNavs.add(i);
		}
		result.setPageNavs(pageNavs);
		List<SearchResult.NavsVo> navsVos = new ArrayList<>();
		// 构造面包屑
		if (!CollectionUtils.isEmpty(param.getAttrs())) {
			navsVos = param.getAttrs().stream().map(attr -> {
				SearchResult.NavsVo navsVo = new SearchResult.NavsVo();
				String[] attrStrs = attr.split("_");
				navsVo.setNavValue(attrStrs[1]);
				Long attrId = Long.parseLong(attrStrs[0]);
				// 需要远程调用商品服务
				R r = productFeignService.attrInfo(attrId);
				result.getAttrIds().add(attrId);
				if (r.getCode().equals(0)) {
					AttrTo attrTo = r.getData("attr", new TypeReference<AttrTo>() {
					});
					navsVo.setNavName(attrTo.getAttrName());
				} else {
					navsVo.setNavName(attrStrs[0]);
				}
				// 取消了面包屑以后，需要跳转到哪里。需要将请求地址url 里面的当前条件替换掉
				// 拿到我们的所有条件。去掉当前条件
				String replace = replaceQueryString(param, attr, "attrs");
				navsVo.setLink("http://search.xxsc.com/list.html?" + replace);
				return navsVo;
			}).collect(Collectors.toList());

			result.setNavs(navsVos);
		}

		if (!CollectionUtils.isEmpty(param.getBrandId())) {
			List<SearchResult.NavsVo> navs = result.getNavs();
			SearchResult.NavsVo navsVo = new SearchResult.NavsVo();
			navsVo.setNavName("品牌");
			// TODO远程调用
			R r = productFeignService.brandinfos(param.getBrandId());
			if (r.getCode().equals(0)) {
				List<BrandTo> brandTo = r.getData("brandList", new TypeReference<List<BrandTo>>() {
				});
				StringBuffer buffer = new StringBuffer();
				String replace = "";
				for (BrandTo brand : brandTo) {
					buffer.append(brand.getName() + ";");
					replace = replaceQueryString(param, brand.getBrandId() + "", "brandId");
				}
				navsVo.setNavValue(buffer.toString());
				navsVo.setLink("http://search.xxsc.com/list.html?" + replace);

			}
			navs.add(navsVo);
			result.setNavs(navs);
		}
		//TODO 分类还需要做

		return result;
	}

	private String replaceQueryString(SearchParam param, String value, String key) {
		String encode = null;
		try {
			encode = URLEncoder.encode(value, "UTF-8");
			// 浏览器与java的空格编码不一样
			encode.replace("+", "%20");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		String replace = param.getQuerString().replace("&" + key + "=" + encode, "");
		return replace;
	}


	/**
	 * 查询构造器,准备检索请求
	 * 模糊查询，过滤(按属性，分类，品牌，价格区间，库存)，排序，分页，高亮，聚合分析
	 *
	 * @param param
	 * @return
	 */
	private SearchRequest buildSearchRequrest(SearchParam param) {
		// 构建语句DSL
		SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

		/**
		 * 查询：模糊查询，过滤(按属性，分类，品牌，价格区间，库存)
		 */

		// 1.构建了 bool - query
		BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
		// 1.1 must
		// 1.1.1 根据检索条件进行查询
		if (!StringUtils.isEmpty(param.getKeyword())) {
			boolQuery.must(QueryBuilders.matchQuery(EsConstant.SKU_TITLE, param.getKeyword()));
		}

		// 1.2 filter
		// 1.2.1 根据三级id进行查询
		if (param.getCatalog3Id() != null) {
			boolQuery.filter(QueryBuilders.termQuery(EsConstant.CATALOG_ID, param.getCatalog3Id()));
		}
		// 1.2.2 根据品牌的id进行查询
		if (!CollectionUtils.isEmpty(param.getBrandId())) {
			boolQuery.filter(QueryBuilders.termsQuery(EsConstant.BRAND_ID, param.getBrandId()));
		}
		// 1.2.2 根据属性进行查询
		if (!CollectionUtils.isEmpty(param.getAttrs())) {
			for (String attrStr : param.getAttrs()) {
				BoolQueryBuilder nestedboolQuery = QueryBuilders.boolQuery();
				String[] attrss = attrStr.split(UNDERSCORE);
				// 检索的属性id
				String attrId = attrss[0];
				// 这个属性用的检索值
				String[] attrValues = attrss[1].split(COLON);
				nestedboolQuery.must(QueryBuilders.termQuery(EsConstant.ATTRS_ATTR_ID, attrId));
				nestedboolQuery.must(QueryBuilders.termsQuery(EsConstant.ATTRS_ATTR_VALUE, attrValues));
				// 每个属性都要生成一个
				NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery(EsConstant.ATTRS, nestedboolQuery, ScoreMode.None);
				boolQuery.filter(nestedQuery);
			}
		}
		// 1.2.2 根据库存是否有进行查询
		if (param.getHasStock() != null) {
			boolQuery.filter(QueryBuilders.termsQuery(EsConstant.HAS_STOCK, param.getHasStock().equals(1)));
		}
		// 1.2.2 根据价格区间有进行查询
		if (!StringUtils.isEmpty(param.getSkuPrice())) {
			RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(EsConstant.SKU_PRICE);
			String[] prices = param.getSkuPrice().split(UNDERSCORE);
			if (prices.length == 2) {
				// 表示是区间
				rangeQuery.gte(prices[0]).lte(prices[1]);
			} else if (prices.length == 1) {
				if (param.getSkuPrice().startsWith(UNDERSCORE)) {
					rangeQuery.lte(prices[0]);
				} else if (param.getSkuPrice().endsWith(UNDERSCORE)) {
					rangeQuery.gte(prices[0]);
				} else {
					log.error("有异常数据传入！！");
				}
			}
			boolQuery.filter(rangeQuery);
		}
		sourceBuilder.query(boolQuery);

		/**
		 * 排序，分页，高亮
		 */
		// 2.1 排序
		if (!StringUtils.isEmpty(param.getSort())) {
			String sort = param.getSort();
			String[] sorStr = sort.split(UNDERSCORE);
			SortOrder sortOrder = sorStr[1].equalsIgnoreCase(ASC) ? SortOrder.ASC : SortOrder.DESC;
			sourceBuilder.sort(sorStr[0], sortOrder);
		}
		// 2.2 分页   pageSize5
		sourceBuilder.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAG_SIZE);
		sourceBuilder.size(EsConstant.PRODUCT_PAG_SIZE);

		// 2.3 高亮
		if (!StringUtils.isEmpty(param.getKeyword())) {
			HighlightBuilder highlightBuilder = new HighlightBuilder();
			highlightBuilder.field(EsConstant.SKU_TITLE);
			highlightBuilder.preTags("<b style='color:red'>");
			highlightBuilder.postTags("</b>");
			sourceBuilder.highlighter(highlightBuilder);
		}
		/**
		 * 聚合分析
		 */
		// 2.4 品牌聚合
		TermsAggregationBuilder brandAggBuilder = AggregationBuilders.terms(EsConstant.BRAND_AGG);
		brandAggBuilder.field(EsConstant.BRAND_ID).size(50);
		// 2.4.2 品牌子聚合

		brandAggBuilder.subAggregation(AggregationBuilders.terms(EsConstant.BRAND_NAME_AGG).field(EsConstant.BRAND_NAME).size(1));
		brandAggBuilder.subAggregation(AggregationBuilders.terms(EsConstant.BRAND_IMG_AGG).field(EsConstant.BRAND_IMG).size(1));
		sourceBuilder.aggregation(brandAggBuilder);
		// 2.4.3 分类聚合
		TermsAggregationBuilder catalogAggBuilder = AggregationBuilders.terms(EsConstant.CATALOG_AGG).field(EsConstant.CATALOG_ID).size(20);
		catalogAggBuilder.subAggregation(AggregationBuilders.terms(EsConstant.CATALOG_NAME_AGG).field(EsConstant.CATALOG_NAME).size(1));
		sourceBuilder.aggregation(catalogAggBuilder);
		// 2.4.4 属性聚合
		NestedAggregationBuilder AttrAgg = AggregationBuilders.nested(EsConstant.ATTR_AGG, EsConstant.ATTRS);

		TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms(EsConstant.ATTRS_ATTR_ID_AGG).field(EsConstant.ATTRS_ATTR_ID);

		attrIdAgg.subAggregation(AggregationBuilders.terms(EsConstant.ATTRS_ATTR_NAME_AGG).field(EsConstant.ATTRS_ATTR_NANE).size(1));
		attrIdAgg.subAggregation(AggregationBuilders.terms(EsConstant.ATTRS_ATTR_VALUE_AGG).field(EsConstant.ATTRS_ATTR_VALUE).size(1));

		AttrAgg.subAggregation(attrIdAgg);
		sourceBuilder.aggregation(AttrAgg);
		// TODO es 查询dsl
//		System.out.println("构建的DSL" + sourceBuilder.toString());
		SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
		return searchRequest;
	}
}
